package com.karaku.karaku_collector.common.http;

import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;

import com.karaku.karaku_collector.BuildConfig;
import com.karaku.karaku_collector.util.LogUtil;

import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * Created by guoping on 2017/4/22.
 */

public class NetOperation<T> {

    private static final String ENC_KEY = "kar(aku~cup@ni~d";

    private static Handler mHandler = new Handler(Looper.getMainLooper());
    private String mMetaData;
    private String mMethod;
    private boolean mIsCanceled;
    private boolean mSuccess = false;
    private Map<String, String> mParams;
    private Map<String, String> mHeader;
    private NetCallback<T>[] mCallbacks;
    private Call mCall;

    private TrustAllCerts mTrustManager = new TrustAllCerts();
    private SSLSocketFactory mSSLSocketFactory;

    public NetOperation(String method, Map<String, String> params) {
        mMethod = method;
        mParams = params;
        mHeader = HeaderCreator.create();
        createSig(mParams, mHeader);
    }

    public void get(String path, Type typeOfT, NetCallback<T>... callbacks) {
        mCallbacks = callbacks;
        Request.Builder b = new Request.Builder().url(getUrl(mMethod, mParams, true));
        initHeader(b);
        final Request request = b.build();
        doReq(path, typeOfT, request);
    }

    public void get(Type typeOfT, NetCallback<T>... callbacks) {
        get(null, typeOfT, callbacks);
    }

    public void post(String path, Type typeOfT, NetCallback<T>... callbacks) {
        mCallbacks = callbacks;
        FormBody.Builder builder = new FormBody.Builder();
        for (String key : mParams.keySet()) {
            builder.add(key, mParams.get(key));
        }
        FormBody formBody = builder.build();
        Request.Builder b = new Request.Builder()
                .url(getUrl(mMethod, mParams, false))
                .post(formBody);
        initHeader(b);
        final Request request = b.build();
        LogUtil.d("NETWORK", "post url:" + getUrl(mMethod, mParams, false));
        doReq(path, typeOfT, request);
    }

    public void post(Type typeOfT, NetCallback<T>... callbacks) {
        post(null, typeOfT, callbacks);
    }

    public <T1> T1 getDataFromResult(String path, Type typeOfT, T1 defaultValue) {
        if (!mSuccess) {
            return defaultValue;
        }
        return (T1) JsonFinder.find(mMetaData, path, typeOfT);
    }

    public void cancel() {
        mIsCanceled = true;
        mCall.cancel();
    }

    public static OkHttpClient getHttpClient() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
        return builder.build();
    }

    private void doReq(String path, final Type typeOfT, final Request request) {
        final String p = TextUtils.isEmpty(path) ? "data" : path;
        OkHttpClient.Builder b = new OkHttpClient.Builder()
                .connectTimeout(2, TimeUnit.MINUTES)
                .writeTimeout(2, TimeUnit.MINUTES)
                .readTimeout(2, TimeUnit.MINUTES)
                .sslSocketFactory(getSSLSocketFactory(), mTrustManager)
                .hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
        OkHttpClient mOkHttpClient = b.build();
        mCall = mOkHttpClient.newCall(request);
        mCall.enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                NetError error = new NetError();
                error.type = mIsCanceled ? NetError.TYPE_CANCEL : NetError.TYPE_NET;
                onError(error);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                mMetaData = response.body().string();
                LogUtil.d("NETWORK", "\nmethod:" + mMethod + "\nurl:" + getUrl(mMethod, mParams, true) + "\nhttp status:" + response.code() + "\ndata:" + mMetaData);

                if (response.code() < 200 || response.code() >= 300) {
                    NetError error = new NetError();
                    error.type = NetError.TYPE_HTTP;
                    error.code = response.code();
                    error.msg = response.message();
                    onError(error);
                } else {
                    JsonFinder finder = null;
                    try {
                        finder = new JsonFinder(mMetaData);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                        NetError error = new NetError();
                        error.type = NetError.TYPE_PARSE;
                        onError(error);
                        return;
                    }
                    int code = finder.find("code", int.class);
                    String msg = finder.find("codeMsg", String.class);
                    String token = finder.find("token", String.class);
                    if (!TextUtils.isEmpty(token)) {
                        NewTokenHandler.onGetNewToken(token);
                    }
                    if (code == 0) {
                        if (typeOfT != Void.class) {
                            T result = null;
                            try {
                                result = finder.find(p, typeOfT);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            if (result == null) {
                                NetError error = new NetError();
                                error.type = NetError.TYPE_PARSE;
                                onError(error);
                            } else {
                                onSuccess(result);
                            }
                        } else {
                            onSuccess(null);
                        }
                    } else {
                        NetError error = new NetError();
                        error.type = NetError.TYPE_LOGIC;
                        error.code = code;
                        error.msg = msg;
                        onError(error);

                        NetErrorHandler.handle(error);
                    }
                }
            }
        });
    }

    private void initHeader(Request.Builder builder) {
        builder.removeHeader("User-Agent");
        builder.addHeader("User-Agent", "HappyCatching");
        for (String key : mHeader.keySet()) {
            builder.addHeader(key, mHeader.get(key));
        }
    }

    private void onError(final NetError error) {
        if (mCallbacks != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    for (NetCallback<T> c : mCallbacks) {
                        if (c != null) {
                            c.failed(NetOperation.this, error);
                        }
                    }
                }
            });
        }
    }

    private void onSuccess(final T result) {
        mSuccess = true;
        if (mCallbacks != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    for (NetCallback<T> c : mCallbacks) {
                        if (c != null) {
                            c.success(NetOperation.this, result);
                        }
                    }
                }
            });
        }
    }

    public static String getUrl(String method, Map<String, String> params, boolean isGetMethod) {
        if (isGetMethod && params != null && params.size() > 0) {
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("%s/%s?", BuildConfig.HOST, method));
            sb.append(getParams(params));
            return sb.toString();
        } else {
            return String.format("%s/%s", BuildConfig.HOST, method);
        }
    }

    private static String getParams(Map<String, String> params) {
        StringBuilder sb = new StringBuilder();
        if (params != null && params.size() > 0) {
            for (String key : params.keySet()) {
                String newKey = key;
                String newValue = params.get(key);
                try {
                    newKey = URLEncoder.encode(newKey, "UTF-8");
                    newValue = URLEncoder.encode(newValue, "UTF-8");
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                sb.append(String.format("%s=%s&", newKey, newValue));
            }
            sb.delete(sb.length() - 1, sb.length());
        }
        return sb.toString();
    }

    private void createSig(Map<String, String> inParams, Map<String, String> header) {
//        Map<String, String> map = new HashMap<>(inParams.size() + header.size());
//        map.putAll(inParams);
//        map.putAll(header);
//        TreeSet<String> treeSet = new TreeSet<>(map.keySet());
//        StringBuilder sb = new StringBuilder(1000);
//        sb.append(ENC_KEY);
//        for (String key : treeSet) {
//            if (!(key.equals("token") || key.equals("uid"))) {
//                String value = map.get(key);
//                sb.append(String.format("%s%s", key, value));
//            }
//        }
//        if (BuildConfig.DEBUG) {
//            LogUtil.d("sign加密前字符串：" + sb.toString());
//        }
//        header.put("sign", SecurityUtil.md5(sb.toString()));
    }

    private SSLSocketFactory getSSLSocketFactory() {
        if (mSSLSocketFactory == null) {
            try {
                SSLContext sc = SSLContext.getInstance("TLS");
                sc.init(null, new TrustManager[]{mTrustManager}, new SecureRandom());
                mSSLSocketFactory = sc.getSocketFactory();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return mSSLSocketFactory;
    }

    private static class TrustAllCerts implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
}

